package com.mt.roll.opcua.s7.my.s7connector.service;

import com.mt.roll.opcua.s7.my.s7connector.api.DaveArea;
import com.mt.roll.opcua.s7.my.s7connector.api.S7Connector;
import com.mt.roll.opcua.s7.my.s7connector.api.utils.ByteUtils;
import com.mt.roll.opcua.s7.my.s7connector.enmuc.PlcVarActual;
import com.mt.roll.opcua.s7.my.s7connector.enmuc.S7Client;
import com.mt.roll.opcua.s7.my.s7connector.type.PlcVar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

/**
 * @Desc: ""
 * @Author: caixiang
 * @DATE: 2022/1/17 14:20
 */
//tips： 外部使用这个service的read、write操作 要异常捕捉，然后在catch里面 用logger.info("记录下异常。"),这里的异常是不会被捕捉的只会向外抛。
//注意：
//      ① 单体变量  length 都是1
//      ② 数组变量  length 就是 你要read 或者write数组的长度
//      ③ bitOffset 是针对Bool 来说的，其他变量bitOffset 都是0
//      ④ strSize 这个字段只是给 String变量 和 String[]变量 用的, 这个字段是plc中设置的最大String长度
//      ⑤ String类型 在write的时候禁止 write中文，因为plc中编码并没有采用UTF-8 而是采用ASCII
// （同理也是，当plc中有设置String类型或者StringArray那么告诉他们必须指定长度 通常他们也会指定长度的。）
@Component
public class S7Service {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    S7Service(){

    }

    /**
     * PlcVar(byte[])  转  java对象    对照表
     * 单体变量
     * Bool       ===>      Boolean
     * LREAL      ===>      Double
     * REAL       ===>      Float
     * DATE       ===>      String(yyyy-MM-dd 这种形式的类型)
     * DTL        ===>      String("年-月-日-工作日-时-分-秒" 这种格式)
     * TIME       ===>      Integer(单位 ms)
     * USINT      ===>      Integer
     * SINT       ===>      Integer
     * UINT       ===>      Integer
     * INT        ===>      Integer
     * DINT       ===>      Integer
     * UINT       ===>      Long
     * Byte       ===>      Integer(有符号)(默认)
     *                     Integer(无符号)(后续扩展)
     * Char       ===>      Character
     * WChar      ===>      Character
     * String     ===>      String                                      （特殊）


     * 数组变量
     * BoolArray      ===>      List<Boolean>
     * ByteArray      ===>      List<Byte>
     * WordArray      ===>      List<Integer>
     * DWordArray     ===>      List<Integer>
     * CharArray      ===>      List<Character>
     * SIntArray      ===>      List<Integer>
     * IntArray       ===>      List<Integer>
     * DIntArray      ===>      List<Integer>
     * UIntArray      ===>      List<Integer>
     * USIntArray     ===>      List<Integer>
     * UDIntArray     ===>      List<Long>
     * StringArray    ===>      String[]                （特殊）
     *
     *
     * */
    //注意 这里的PlcVarActual plcVarActual只是一个demo，一个模范写法。。具体项目，多plc 还是不同的
    public Object read(PlcVarActual plcVarActual,S7Client s7Client) throws UnsupportedEncodingException, ParseException {
        S7Connector connector = s7Client.getConnector();
        //String 类型比较特殊。 String[] 也是同理，Sring数组里面的子项 也是有两个字节的 readBytes。
        if(plcVarActual.getType().equals(PlcVar.STRING)){
            Integer readBytes = 2;
            byte[] read = connector.read(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    readBytes,
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    plcVarActual.getType().getTransportSize()
            );

            Integer allLength = Integer.valueOf(read[1])+2;
            byte[] readF = connector.read(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    allLength,
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    plcVarActual.getType().getTransportSize()
            );
            return plcVarActual.getType().toObject(readF);
        }else if(plcVarActual.getType().equals(PlcVar.STRING_Array)){
            Integer arrayLength =  plcVarActual.getLength();
            Integer strSize =  plcVarActual.getStrSize();

            byte[] read = connector.read(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    arrayLength*(strSize+2),
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    plcVarActual.getType().getTransportSize()
            );
            return ByteUtils.toStrArray(read, arrayLength, strSize);
        }else {
            Integer readBytes = plcVarActual.getType().getTransportSize().getSizeInBytes() * plcVarActual.getLength();
            byte[] read = connector.read(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    readBytes,
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    plcVarActual.getType().getTransportSize()
            );
            return plcVarActual.getType().toObject(read);
        }
    }
    public Object read(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset, Integer length, Integer strSizes, PlcVar type,S7Client s7Client) throws UnsupportedEncodingException, ParseException {
        S7Connector connector = s7Client.getConnector();
        //String 类型比较特殊。 String[] 也是同理，Sring数组里面的子项 也是有两个字节的 readBytes。
        if(type.equals(PlcVar.STRING)){
            Integer readBytes = 2;
            byte[] read = connector.read(
                    area,
                    areaNumber,
                    readBytes,
                    byteOffset,
                    bitOffset,
                    type.getTransportSize()
            );

            Integer allLength = Integer.valueOf(read[1])+2;
            byte[] readF = connector.read(
                    area,
                    areaNumber,
                    allLength,
                    byteOffset,
                    bitOffset,
                    type.getTransportSize()
            );
            return type.toObject(readF);
        }else if(type.equals(PlcVar.STRING_Array)){
            Integer arrayLength =  length;
            Integer strSize =  strSizes;

            byte[] read = connector.read(
                    area,
                    areaNumber,
                    arrayLength*(strSize+2),
                    byteOffset,
                    bitOffset,
                    type.getTransportSize()
            );
            return ByteUtils.toStrArray(read, arrayLength, strSize);
        }else {
            Integer readBytes = type.getTransportSize().getSizeInBytes() * length;
            byte[] read = connector.read(
                    area,
                    areaNumber,
                    readBytes,
                    byteOffset,
                    bitOffset,
                    type.getTransportSize()
            );
            return type.toObject(read);
        }
    }

    /**
     *
     * eg :
     *      Object newValue = Boolean.FALSE
     *      s7Service.write(PlcVarActual.HeartBeat, newValue, S7Client.S7_1200);
     *
     * PlcVar(byte[])  转  java对象    对照表
     * 单体变量
     * Bool       ===>      Object newValue = Boolean.FALSE
     * LREAL      ===>      Object newValue = Boolean.FALSE
     * REAL       ===>      Object newValue = Boolean.FALSE
     * DATE       ===>      暂时没需求（有问题找我）
     * DTL        ===>      暂时没需求（有问题找我）
     * TIME       ===>      暂时没需求（有问题找我）
     * USINT      ===>      Object newValue = new Integer(1)
     * SINT       ===>      Object newValue = new Integer(1)
     * UINT       ===>      Object newValue = new Integer(1)
     * INT        ===>      Object newValue = new Integer(1)
     * DINT       ===>      Object newValue = new Integer(1)
     * UINT       ===>      Object newValue = new Integer(1)
     * Byte       ===>      Object newValue = 0x11
     *
     * Char       ===>      Object newValue = 'a'
     * WChar      ===>      Object newValue = '菜'
     * String     ===>      Object newValue = '你好啊'


     * 数组变量
     *      注意：在write的时候，你write的数量  一定要和  plc中存在的数量一一对应
     * BoolArray      ===>      boolean[] booleanArray = new boolean[2];  .... 赋予值
     * ByteArray      ===>      byte[] write_byteArrays = new byte[2];
     * WordArray      ===>      short[] shortArrays_content = new short[2];
     * DWordArray     ===>      int[] intArrays_content = new int[2];
     * CharArray      ===>      char[] charArrays_content = new char[2];
     * SIntArray      ===>      int[] sintArrays_content = new int[2];
     * IntArray       ===>      int[] iintArrays_content = new int[2];
     * DIntArray      ===>      int[] dintArrays_content = new int[2];
     * UIntArray      ===>      int[] uintArrays_content = new int[3];
     * USIntArray     ===>      int[] usintArrays_content = new int[3];
     * UDIntArray     ===>      int[] udintArrays_content = new int[3];
     * //如果有其他数据类型  这里没有找我扩展
     *
     *
     * */
    public void write(PlcVarActual plcVarActual,Object newValue,S7Client s7Client){
        S7Connector connector = s7Client.getConnector();

        //String 类型比较特殊。 String[] 也是同理，Sring数组里面的子项 也是有两个字节的 readBytes。
        if(plcVarActual.getType().equals(PlcVar.STRING)){
            connector.write(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    ByteUtils.strToBytes(newValue.toString(), plcVarActual.getStrSize()),
                    plcVarActual.getType()
            );
        }else if(plcVarActual.getType().equals(PlcVar.STRING_Array)){
            //todo here 检查 read write service
            connector.write(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    ByteUtils.strArrayToBytes((String[])newValue, plcVarActual.getStrSize()),
                    plcVarActual.getType()
            );
        }else {
            byte[] bytes = plcVarActual.getType().toBytes(newValue);
            connector.write(
                    plcVarActual.getArea(),
                    plcVarActual.getAreaNumber(),
                    plcVarActual.getByteOffset(),
                    plcVarActual.getBitOffset(),
                    bytes,
                    plcVarActual.getType()
            );
        }
    }
    public void write(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset,Integer strSize, PlcVar type, Object newValue, S7Client s7Client){
        S7Connector connector = s7Client.getConnector();

        //String 类型比较特殊。 String[] 也是同理，Sring数组里面的子项 也是有两个字节的 readBytes。
        if(type.equals(PlcVar.STRING)){
            connector.write(
                    area,
                    areaNumber,
                    byteOffset,
                    bitOffset,
                    ByteUtils.strToBytes(newValue.toString(), strSize),
                    type
            );
        }else if(type.equals(PlcVar.STRING_Array)){
            //todo here 检查 read write service
            connector.write(
                    area,
                    areaNumber,
                    byteOffset,
                    bitOffset,
                    ByteUtils.strArrayToBytes((String[])newValue, strSize),
                    type
            );
        }else {
            byte[] bytes = type.toBytes(newValue);
            connector.write(
                    area,
                    areaNumber,
                    byteOffset,
                    bitOffset,
                    bytes,
                    type
            );
        }
    }

}
